package nachos.threads;

import java.util.LinkedList;

import nachos.machine.*;
import nachos.security.Privilege;


/**
 * Uses the hardware timer to provide preemption, and to allow threads to sleep
 * until a certain time.
 */
public class Alarm {
    /**
     * Allocate a new Alarm. Set the machine's timer interrupt handler to this
     * alarm's callback.
     *
     * <p><b>Note</b>: Nachos will not function correctly with more than one
     * alarm.
     */
    public Alarm() {
	Machine.timer().setInterruptHandler(new Runnable() {
		public void run() { timerInterrupt(); }
	    });
	
	waitQueue = new SynchList();
    }

    /**
     * The timer interrupt handler. This is called by the machine's timer
     * periodically (approximately every 500 clock ticks). Causes the current
     * thread to yield, forcing a context switch if there is another thread
     * that should be run.
     */
    public void timerInterrupt() {
    	boolean intStatus = Machine.interrupt().disable();
    	Lib.debug(dbgAlarm, "timerInterrupt at" + Machine.timer().getTime());
    	
    	LinkedList<KThread> stillInQueue = new LinkedList<KThread>();
    	while(waitQueue.size() > 0){
    		KThread t = (KThread)waitQueue.removeFirst();
    		Lib.debug(dbgAlarm, "Thread wake-up time: " + t.getWaitTime());
    		if(Machine.timer().getTime() >= t.getWaitTime()){
    			t.ready();
    			Lib.debug(dbgAlarm, "Woke up thread " + t.getName() + " at " + Machine.timer().getTime());
    		}else{
    			stillInQueue.add(t);
    		}
    	}

    	for(KThread t : stillInQueue){
    		waitQueue.add(t);
    	}
    	Machine.interrupt().restore(intStatus);
    }

    /**
     * Put the current thread to sleep for at least <i>x</i> ticks,
     * waking it up in the timer interrupt handler. The thread must be
     * woken up (placed in the scheduler ready set) during the first timer
     * interrupt where
     *
     * <p><blockquote>
     * (current time) >= (WaitUntil called time)+(x)
     * </blockquote>
     *
     * @param	x	the minimum number of clock ticks to wait.
     *
     * @see	nachos.machine.Timer#getTime()
     */
    public void waitUntil(long x) {
	
    long waitTime = Machine.timer().getTime() + x;
    KThread.currentThread().setWaitTime(waitTime);
    boolean intStatus = Machine.interrupt().disable();
    waitQueue.add(KThread.currentThread());
    Lib.debug(dbgAlarm, "Sleeping: " + KThread.currentThread().getName() +
    				" for " + waitTime + " at " + Machine.timer().getTime()); 
    KThread.currentThread().sleep();
    
    Lib.debug(dbgAlarm, "woke up: " + KThread.currentThread().getName() +" at " + Machine.timer().getTime());    
    // for now, cheat just to get something working (busy waiting is bad)
    /*
    long wakeTime = Machine.timer().getTime() + x;
	while (wakeTime > Machine.timer().getTime())
	    KThread.yield();
    
    */
    Machine.interrupt().restore(intStatus);
    }
    
    public static void selfTest(){
    	new KThread(new PingTest(0)).setName("ping").fork();
    	new PingTest(0).run();
    }
    
    private static class PingTest implements Runnable {
    	PingTest(int which) {
    	    this.which = which;
    	   
    	}
    	
    	public void run() {
    	    for (int i=0; i<5000; i++) {
    		System.out.println("*** thread " + which + " looped "
    				   + i + " times");
    			ThreadedKernel.alarm.waitUntil(1000);
    	    }
    	}
    	private int which;
    	
    }
    
    public static SynchList waitQueue;
    private static final char dbgAlarm = 'A';
}
